import sys
import warnings
import shutil
import socket

from contextlib import contextmanager
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.webdriver.firefox.extension_connection import ExtensionConnection
from selenium.webdriver.firefox.firefox_binary import FirefoxBinary
from selenium.webdriver.firefox.firefox_profile import FirefoxProfile
from selenium.webdriver.firefox.options import Options as FirefoxOptions
from selenium.webdriver.firefox.remote_connection import FirefoxRemoteConnection
from selenium.webdriver.firefox.service import Service as FirefoxService

from ..remote.webdriver import WebDriver as HSWebDriver
from .webelement import HSFirefoxWebElement

try:
    import http.client as http_client
except ImportError:
    import httplib as http_client


class WebDriver(HSWebDriver):

    # There is no native event support on Mac
    NATIVE_EVENTS_ALLOWED = sys.platform != "darwin"
    CONTEXT_CHROME = "chrome"
    CONTEXT_CONTENT = "content"
    _web_element_cls = HSFirefoxWebElement

    def __init__(self, firefox_profile=None, firefox_binary=None, timeout=30, capabilities=None, proxy=None,
                 executable_path="geckodriver", options=None, log_path="geckodriver.log", firefox_options=None,
                 service_args=None, desired_capabilities=None):
        if firefox_options:
            warnings.warn('use options instead of firefox_options', DeprecationWarning)
            options = firefox_options
        self.binary = None
        self.profile = None
        self.service = None

        # If desired capabilities is set, alias it to capabilities.
        # If both are set ignore desired capabilities.
        if capabilities is None and desired_capabilities:
            capabilities = desired_capabilities

        if capabilities is None:
            capabilities = DesiredCapabilities.FIREFOX.copy()
        if options is None:
            options = FirefoxOptions()

        capabilities = dict(capabilities)

        if capabilities.get("binary"):
            self.binary = capabilities["binary"]

        # options overrides capabilities
        if options is not None:
            if options.binary is not None:
                self.binary = options.binary
            if options.profile is not None:
                self.profile = options.profile

        # firefox_binary and firefox_profile
        # override options
        if firefox_binary is not None:
            if isinstance(firefox_binary, str):
                firefox_binary = FirefoxBinary(firefox_binary)
            self.binary = firefox_binary
            options.binary = firefox_binary
        if firefox_profile is not None:
            if isinstance(firefox_profile, str):
                firefox_profile = FirefoxProfile(firefox_profile)
            self.profile = firefox_profile
            options.profile = firefox_profile

        # W3C remote
        # TODO(ato): Perform conformance negotiation

        if capabilities.get("marionette"):
            capabilities.pop("marionette")
            self.service = FirefoxService(
                executable_path,
                service_args=service_args,
                log_path=log_path)
            self.service.start()

            capabilities.update(options.to_capabilities())

            executor = FirefoxRemoteConnection(
                remote_server_addr=self.service.service_url)
            HSWebDriver.__init__(
                self,
                command_executor=executor,
                desired_capabilities=capabilities,
                keep_alive=True)

        # Selenium remote
        else:
            if self.binary is None:
                self.binary = FirefoxBinary()
            if self.profile is None:
                self.profile = FirefoxProfile()

            # disable native events if globally disabled
            self.profile.native_events_enabled = (
                self.NATIVE_EVENTS_ALLOWED and self.profile.native_events_enabled)

            if proxy is not None:
                proxy.add_to_capabilities(capabilities)

            executor = ExtensionConnection("127.0.0.1", self.profile,
                                           self.binary, timeout)
            HSWebDriver.__init__(
                self,
                command_executor=executor,
                desired_capabilities=capabilities,
                keep_alive=True)

        self._is_remote = False

    def quit(self):
        """Quits the driver and close every associated window."""
        try:
            HSWebDriver.quit(self)
        except (http_client.BadStatusLine, socket.error):
            # Happens if Firefox shutsdown before we've read the response from
            # the socket.
            pass

        if self.w3c:
            self.service.stop()
        else:
            self.binary.kill()

        if self.profile is not None:
            try:
                shutil.rmtree(self.profile.path)
                if self.profile.tempfolder is not None:
                    shutil.rmtree(self.profile.tempfolder)
            except Exception as e:
                print(str(e))

    @property
    def firefox_profile(self):
        return self.profile

    # Extension commands:

    def set_context(self, context):
        self.execute("SET_CONTEXT", {"context": context})

    @contextmanager
    def context(self, context):
        """Sets the context that Selenium commands are running in using
        a `with` statement. The state of the context on the server is
        saved before entering the block, and restored upon exiting it.

        :param context: Context, may be one of the class properties
            `CONTEXT_CHROME` or `CONTEXT_CONTENT`.

        Usage example::

            with selenium.context(selenium.CONTEXT_CHROME):
                # chrome scope
                ... do stuff ...
        """
        initial_context = self.execute('GET_CONTEXT').pop('value')
        self.set_context(context)
        try:
            yield
        finally:
            self.set_context(initial_context)

    def install_addon(self, path, temporary=None):
        """
        Installs Firefox addon.

        Returns identifier of installed addon. This identifier can later
        be used to uninstall addon.

        :param path: Absolute path to the addon that will be installed.

        :Usage:
            driver.install_addon('/path/to/firebug.xpi')
        """
        payload = {"path": path}
        if temporary is not None:
            payload["temporary"] = temporary
        return self.execute("INSTALL_ADDON", payload)["value"]

    def uninstall_addon(self, identifier):
        """
        Uninstalls Firefox addon using its identifier.

        :Usage:
            driver.uninstall_addon('addon@foo.com')
        """
        self.execute("UNINSTALL_ADDON", {"id": identifier})
